using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace my_console5;

/// <summary>
/// ES 查询参数
/// </summary>
public static class AggInput
{
    private static string[] _ignoredFilterFields =
    [
        StatFilterTypes.InstitutionalLevel,
        StatFilterTypes.NoDataOrgIncluded,
        StatFilterTypes.HospitalCategory,
        StatFilterTypes.HospitalOwnership,
        StatFilterTypes.HospitalTag
    ];

    /// <summary>
    /// 异步查询
    /// </summary>
    /// <param name="indexName">索引名</param>
    /// <param name="para">查询参数</param>
    /// <returns></returns>
    public static async Task<JArray> SearchAsync(string indexName, StatPara para)
    {
        var query = Parse(para);
        var res = await ElasticHelper.SearchAsync(indexName, query.ToString());
        var result = ResultHandle(res);
        return result;
    }

    /// <summary>
    /// 将参数转化为 ES 查询 JSON 对象
    /// </summary>
    /// <param name="para"></param>
    /// <returns></returns>
    public static JObject Parse(StatPara para)
    {
        var obj = new JObject();

        Filter(obj, para);
        Group(obj, para);
        Metric(obj);
        Other(obj, para);

        return obj;
    }

    /// <summary>
    /// 查询结果处理
    /// </summary>
    /// <param name="json"></param>
    /// <returns></returns>
    public static JArray ResultHandle(JObject json)
    {
        if (json["aggregations"]["multi_field_group"]["buckets"] is not JArray aggs || aggs.Count == 0) return [];
        var result = new JArray();
        aggs.ToList().ForEach(item =>
        {
            var obj = new JObject();
            item["key"].Children<JProperty>().ToList().ForEach(a =>
            {
                obj[a.Name] = a.Value;
            });
            var metrics = getMetrics().Where(a => (a.Category & EsMetricCategory.Calc) > 0).ToList();
            metrics.ForEach(metric =>
            {
                if (metric.Format == null) throw new ArgumentException($"Metric {metric.Key} does not have a format function.");
                metric.Format(obj, item);
            });
            result.Add(obj);
        });
        return result;
    }

    /// <summary>
    /// 数据过滤
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="para"></param>
    static void Filter(JObject obj, StatPara para)
    {
        var must = new JArray();
        obj["query"] = new JObject { ["bool"] = new JObject { ["must"] = must } };
        foreach (var field in para.Filter.Fields)
        {
            if (field.Value == null || field.Value.Length == 0) continue;
            if (_ignoredFilterFields.Contains(field.Key)) continue;         // 排除无需筛选的字段
            var name = parseFieldName(field.Key);                           // 获取 ES 中的字段路径
            var filter = new JObject
            {
                ["terms"] = new JObject { [name] = new JArray(field.Value) }    // 精准匹配
            };
            must.Add(filter);
        }
        filterTime();                                                       // 时间范围过滤

        // 转化为 ES 中的字段路径
        string parseFieldName(string fieldName)
        {
            return fieldName switch
            {
                "Hospital" => "HospitalCode",
                "Modality" => "ModalityCode",
                "ApproveTime" => "Report.ApproveTime",
                _ => fieldName
            };
        }

        // 时间范围过滤
        void filterTime()
        {
            if (para.Filter.TimeRange == null)
                return;
            var fileName = parseFieldName(para.Filter.TimeRange.TimeType);
            must.Add(
                new JObject
                {
                    ["range"] = new JObject
                    {
                        [fileName] = new JObject
                        {
                            ["from"] = para.Filter.TimeRange.Value[0].ToString("yyyy-MM-dd"),
                            ["to"] = para.Filter.TimeRange.Value[1].ToString("yyyy-MM-dd")
                        }
                    }
                }
            );
        }
    }

    /// <summary>
    /// 公共统计分组
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="para"></param>
    /// <exception cref="ArgumentException"></exception>
    static void Group(JObject obj, StatPara para)
    {
        var sources = new JArray();
        // 采用 Composite 聚合方式进行多字段分组
        var agg = new JObject { ["multi_field_group"] = new JObject { ["composite"] = new JObject { ["sources"] = sources, ["size"] = 10000 } } };

        foreach (var group in para.Groups)
        {
            var field = parseGroup(group.Key);
            sources.Add(field);
        }

        obj["aggs"] = agg;


        // 转化为 ES 中的字段路径
        JObject parseGroup(string fieldName)
        {
            return fieldName switch
            {
                "Hospital" => getEsGroup("Hospital", "HospitalCode"),
                "HospitalBranch" => getEsGroup("HospitalBranch", "HospitalBranchCode"),
                "HospitalBranchName" => getEsGroup("HospitalBranchName", "HospitalBranchName"),
                "Modality" => getEsGroup("Modality", "ModalityCode"),
                "ExecDepartment" => getEsGroup("ExecDepartment", "Order.ExecDepartmentName"),
                "PatientType" => getEsGroup("PatientType", "Visit.PatientType"),
                "ApproveTimeByDay" => getEsGroup("ApproveTimeByDay", "Report.ApproveTime", EsGroupType.Date),
                "UploadTimeByDay" => getEsGroup("UploadTimeByDay", "UploadTime", EsGroupType.Date),
                _ => throw new ArgumentException($"Unsupported group field: {fieldName}")
            };
        }

    }

    /// <summary>
    /// 统计指标分组
    /// </summary>
    /// <param name="obj"></param>
    static void Metric(JObject obj)
    {
        var metrics = getMetrics().Where(a => (a.Category & EsMetricCategory.Group) > 0).ToList();
        var json = new JObject();
        metrics.ForEach(a => json[a.Key] = parseGroup(a));
        obj["aggs"]["multi_field_group"]["aggs"] = json;

        static JObject parseGroup(EsMetricData metric)
        {
            return new JObject { ["terms"] = new JObject { ["field"] = metric.FieldName } };
        }
    }

    /// <summary>
    /// 其他操作
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="para"></param>
    static void Other(JObject obj, StatPara para)
    {
        if (para.Groups == null || para.Groups.Length == 0)
            return;
        obj["_source"] = false;
        obj["size"] = 0;
    }

    /// <summary>
    /// 获取报表所有指标
    /// </summary>
    /// <returns></returns>
    static EsMetricData[] getMetrics()
    {
        return [
            new()
            {
                Key = "Count",
                Name = "检查报告数量",
                Category = EsMetricCategory.Calc,
                Format = (obj, source) => obj["Count"] = source["doc_count"]
            },
            new()
            {
                Key = "MatchCount",
                Name = "匹配影像数量",
                FieldName = "Order.IsMatch",
                Category = EsMetricCategory.All,
                Format = (obj, source) => metricByBool(obj, source, "MatchCount")
            },
            new()
            {
                Key = "UnMatchCount",
                Name = "未匹配影像数量",
                Category = EsMetricCategory.Calc,
                Format = (obj, source) => metricByBool(obj, source, "MatchCount", false)
            },
            new()
            {
                Key = "MatchRate",
                Name = "匹配率",
                Category = EsMetricCategory.None,
                CustomPara = "type=percent&totalField=Count&valueField=MatchCount"
            },
            new()
            {
                Key = "ClosedCount",
                Name = "报告闭环数量",
                FieldName = "Tags.EMRInteroperability",
                Category = EsMetricCategory.All,
                Format = (obj, source) => metricByBool(obj, source, "ClosedCount", true)
            }
        ];


        void metricByBool(JObject obj, JToken source, string key, bool flag = true)
        {
            var tag = flag ? 1 : 0;
            var arr = source[key]["buckets"] ?? new JArray();
            var val = arr.FirstOrDefault(a => a["key"]?.Value<int>() == tag);
            obj[key] = val == null ? 0 : val["doc_count"].Value<int>();
        }
    }

    /// <summary>
    /// 根据字段名和类型，获取 ES 分组对象
    /// </summary>
    /// <param name="key"></param>
    /// <param name="fieldName"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    static JObject getEsGroup(string key, string fieldName, EsGroupType type = EsGroupType.String)
    {
        var obj = new JObject();
        var group = new JObject { [key] = obj };
        if (type == EsGroupType.String)
        {
            obj["terms"] = new JObject { ["field"] = fieldName };
        }
        else if (type == EsGroupType.Date)
        {
            obj["date_histogram"] = new JObject { ["field"] = fieldName, ["calendar_interval"] = "day", ["format"] = "yyyy-MM-dd" };
        }
        return group;
    }
}

public class EsMetricData
{
    public string Key { get; set; }
    public string Name { get; set; }
    public string FieldName { get; set; }
    public string CustomPara { get; set; }
    /// <summary>
    /// para1: 输出对象
    /// para2: ES 查询源数据
    /// </summary>
    public Action<JObject, JToken> Format { get; set; }
    public EsMetricCategory Category { get; set; }
}

/// <summary>
/// ES分组类型
/// </summary>
public enum EsGroupType
{
    String,
    Date,
}
/// <summary>
/// ES指标类型
/// </summary>
[Flags]
public enum EsMetricCategory : int
{
    /// <summary>
    /// 无需任何操作
    /// </summary>
    None = 0,
    /// <summary>
    /// 参与分组的指标
    /// </summary>
    Group = 1,
    /// <summary>
    /// 输出结果参与计算的指标
    /// </summary>
    Calc = 2,

    /// <summary>
    /// 既参与字段分组，又参与结果计算的指标
    /// </summary>
    All = 3
}

/// <summary>
/// 聚合查询参数
/// </summary>
public class StatPara
{
    /// <summary>
    /// 筛选器
    /// </summary>
    public StatFilter Filter { get; set; }

    /// <summary>
    /// 分组参数
    /// </summary>
    public StatField[] Groups { get; set; }
}

public class StatFilter
{
    public StatFilterItem[] Fields { get; set; }

    public StatTimeRange TimeRange { get; set; }
}

/// <summary>
/// 字段过滤器
/// </summary>
public class StatFilterItem : StatRecord
{
    public string[] Value { get; set; }
}

/// <summary>
/// 有DbPath的字段
/// </summary>
public class StatRecord
{
    [JsonProperty("key")]
    public string Key { get; set; }

    [JsonProperty("label")]
    public string Label { get; set; }

    /// <summary>
    /// 数据库路径 (不传给前端)
    /// </summary>
    [JsonIgnore]
    public string DbPath { get; set; }

    public StatRecord() { }

    public StatRecord(string key, string label, string dbPath)
    {
        this.Key = key;
        this.Label = label;
        this.DbPath = dbPath;
    }
}

/// <summary>
/// 时间过滤器
/// </summary>
public record StatTimeRange
{
    public string TimeType { get; set; }

    public DateTime[] Value { get; set; }

    /// <summary>
    /// 时间范围类型
    /// 为空表示 起始~结束 两个时间
    /// 为‘MultiDays’表示多个独立选择的时间
    /// </summary>
    public string TimeRangeType { get; set; }
}

/// <summary>
/// 统计字段基类
/// </summary>
public class StatField
{
    [JsonProperty("key")]
    public string Key { get; set; }

    [JsonProperty("label")]
    public string Label { get; set; }

    /// <summary>
    /// 是否可见， 设置为false的时候一般用于计算百分比（不显示的字段要参与运算）
    /// </summary>
    [JsonProperty("visible")]
    public bool Visible { get; set; } = true;

    /// <summary>
    /// 自定义参数， 目前只支持百分比
    /// </summary>
    [JsonProperty("customPara")]
    public string CustomPara { get; set; }

    public StatField() { }

    public StatField(string key, string label)
    {
        this.Key = key;
        this.Label = label;
    }

    public override string ToString()
    {
        return Label;
    }

    /// <summary>
    /// 自定义百分比计算列
    /// </summary>
    public static string PercentPara(string valueField, string totalField)
    {
        return $"type=percent&totalField={totalField}&valueField={valueField}";
    }
}

class StatFilterTypes
{
    public const string Hospital = "Hospital";

    public const string Modality = "Modality";

    public const string InstitutionalLevel = "InstitutionalLevel";
    public const string HospitalTag = "HospitalTag";
    public const string NoDataOrgIncluded = "NoDataOrgIncluded";
    public const string HospitalOwnership = "HospitalOwnership";
    public const string HospitalCategory = "HospitalCategory";
}
